﻿using System;
using System.Collections.Generic;
using System.Linq;

namespace CommandLineParser
{
    public class CommandLineOption
    {
        private static char[] FORBIDDEN_CHAR_IN_NAME = { ' ' };

        private static char[] FORBIDDEN_CHAR_IN_ABBREVIATION = { ' ', '-' };

        public string Name { get; private set; }

        public char? Abbreviation { get; private set; }

        public bool AcceptValue { get; private set; }

        public bool AllowMultipltValues { get; private set; }

        public CommandLineOption(string name) : this(name, null, false, false) {; }

        public CommandLineOption(string name, char abbreviation) : this(name, abbreviation, false, false) {; }

        public CommandLineOption(string name, bool acceptValue) : this(name, null, acceptValue, false) {; }

        public CommandLineOption(string name, bool acceptValue, bool allowMultipltValues) : this(name, null, acceptValue, allowMultipltValues) {; }

        public CommandLineOption(string name, char? abbreviation, bool acceptValue, bool allowMultipltValues)
        {
            //进行必要的检查
            if (name.IndexOfAny(FORBIDDEN_CHAR_IN_NAME) >= 0)
                throw new ArgumentException();
            if (abbreviation.HasValue && FORBIDDEN_CHAR_IN_ABBREVIATION.Contains(abbreviation.Value))
                throw new ArgumentException();
            //设置
            Name = name;
            Abbreviation = abbreviation;
            AcceptValue = acceptValue;
            AllowMultipltValues = allowMultipltValues;
        }
    }

    public class CommandLineValidator
    {
        public IList<CommandLineOption> Options { get; private set; } = new List<CommandLineOption>();

        private List<CommandLineResultOption> ResultOptions = new List<CommandLineResultOption>();

        public int AcceptRawValueCount { get; set; }

        public IList<string> RawValues { get; private set; } = new List<string>();

        public bool Contains(string optionName)
        {
            return ResultOptions.Where(o => o.Name == optionName).Any();
        }

        public string GetValue(string optionName)
        {
            var option = ResultOptions.Where(o => o.Name == optionName).FirstOrDefault();
            if (option == null)
                return null;
            return option.Values.FirstOrDefault();
        }

        public string GetValue(string optionName, string defaultValue)
        {
            var option = ResultOptions.Where(o => o.Name == optionName).FirstOrDefault();
            if (option == null)
                return defaultValue;
            string result = option.Values.FirstOrDefault();
            if (result == null)
                return defaultValue;
            return result;
        }

        public IReadOnlyList<string> GetValues(string optionName)
        {
            var option = ResultOptions.Where(o => o.Name == optionName).FirstOrDefault();
            if (option == null)
                return null;
            return option.Values;
        }

        public void ClearResult()
        {
            ResultOptions.Clear();
        }

        public bool TryValidate(IList<CommandLineComponent> components, IList<string> errList)
        {
            //清空
            ResultOptions.Clear();

            //工作变量
            CommandLineOption lastOption = null;
            CommandLineComponent lastComponent = null;
            int acceptedRawValueCount = 0;
            CommandLineComponent eachComponent;

            for (int i = 0; i < components.Count; i++, lastComponent = eachComponent)
            {
                eachComponent = components[i];
                //检查是否为裸值
                if (lastOption == null && !eachComponent.IsOption)
                {
                    if (AcceptRawValueCount >= 0)
                    {
                        if (acceptedRawValueCount >= AcceptRawValueCount)
                        {
                            errList.Add("too many values.");
                            return false;
                        }
                        acceptedRawValueCount++;
                    }
                    RawValues.Add(eachComponent.Value);
                }
                //选项值
                if (lastOption != null && !eachComponent.IsOption)
                {
                    if (!lastOption.AcceptValue && lastComponent.ClungValue)
                    {
                        errList.Add($"option {lastOption.Name} doesn't accept value.");
                        return false;
                    }
                    if (!lastOption.AcceptValue && !lastComponent.ClungValue)
                    {
                        //作为裸值处理
                        lastOption = null;
                        i--;
                        continue;
                    }
                    var existingOption = ResultOptions.Where(o => o.Name == lastOption.Name).FirstOrDefault();
                    if (lastComponent.ClungValue && !lastOption.AllowMultipltValues && existingOption?.Values.Count > 0)
                    {
                        errList.Add($"option {lastOption.Name} doesn't accept multiple values.");
                        return false;
                    }
                    if (!lastOption.AllowMultipltValues && existingOption?.Values.Count > 0)
                    {
                        //作为裸值处理
                        lastOption = null;
                        i--;
                        continue;
                    }
                    if (existingOption == null)
                        ResultOptions.Add(new CommandLineResultOption(lastOption, eachComponent.Value));
                    else
                        existingOption.Values.Add(eachComponent.Value);
                    //下一个作为裸值处理
                    if (lastComponent.ClungValue)
                        lastOption = null;
                }
                //选项
                if (eachComponent.IsFullOption)
                {
                    var existingOption = ResultOptions.Where(o => o.Name == eachComponent.Value).FirstOrDefault();
                    if (existingOption != null)
                    {
                        errList.Add($"duplicate option : {existingOption.Name}.");
                        return false;
                    }
                    lastOption = Options.Where(o => o.Name == eachComponent.Value).FirstOrDefault();
                    if (lastOption == null)
                    {
                        errList.Add($"option not supported : {eachComponent.Value}.");
                        return false;
                    }
                    ResultOptions.Add(new CommandLineResultOption(lastOption));
                }
                if (eachComponent.IsAbbreviatedOption)
                {
                    var existingOption = ResultOptions.Where(o => o.Abbreviation.HasValue && o.Abbreviation.Value.ToString() == eachComponent.Value).FirstOrDefault();
                    if (existingOption != null)
                    {
                        errList.Add($"duplicate option : {existingOption.Name}.");
                        return false;
                    }
                    lastOption = Options.Where(o => o.Abbreviation.HasValue && o.Abbreviation.ToString() == eachComponent.Value).FirstOrDefault();
                    if (lastOption == null)
                    {
                        errList.Add($"option not supported : {eachComponent.Value}.");
                        return false;
                    }
                    ResultOptions.Add(new CommandLineResultOption(lastOption));
                }
            }
            return true;
        }

        public bool TryParseAndValidate(string commandLine, IList<string> errList)
        {
            var result = CommandLineDecomposer.Decompose(commandLine, errList);
            if (errList.Count != 0)
            {
                return false; ;
            }
            return TryValidate(result, errList);
        }

        private class CommandLineResultOption : CommandLineOption
        {
            public List<string> Values { get; private set; } = new List<string>();


            public CommandLineResultOption(CommandLineOption baseOption) : base(baseOption.Name, baseOption.Abbreviation, baseOption.AcceptValue, baseOption.AllowMultipltValues)
            {

            }

            public CommandLineResultOption(CommandLineOption baseOption, string value) : this(baseOption)
            {
                Values.Add(value);
            }
        }
    }


}
